package com.epam.ws.anatomist.messaging.dispatcher;

import java.util.HashMap;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;

import com.epam.ws.anatomist.common.WsAnatomistUtil;
import com.epam.ws.anatomist.common.exception.ErrorMessages;
import com.epam.ws.anatomist.common.exception.MessagingException;
import com.epam.ws.anatomist.common.exception.ServiceInvokerException;
import com.epam.ws.anatomist.message.MessageConverter;

public class ServiceInvoker
{

	private static final Logger LOGGER = Logger.getLogger(ServiceInvoker.class);

	private final HashMap<String, JAXBContext> JAXB_CONTEXT_MAP = new HashMap<String, JAXBContext>();

	private final ServiceFactory SERVICE_FACTORY;

	private ServiceInvoker(ServiceFactory factory)
	{
		this.SERVICE_FACTORY = factory;
	}

	public ServiceInvoker(ApplicationContext context)
	{
		this(new ServiceFactory(context));
	}

	public String invoke(final String serviceName, final String soapRequest)
	{
		StreamSource source = new StreamSource(soapRequest);
		Service service = SERVICE_FACTORY.getService(serviceName);
		QName portQName = SERVICE_FACTORY.getPortQName(serviceName);
		Dispatch<Source> dispSource = service.createDispatch(portQName, Source.class, Service.Mode.MESSAGE);
		Source response = dispSource.invoke(source);
		return MessageConverter.convertSourceToXml(response);
	}

	@SuppressWarnings("unchecked")
	public <RQ, RS> RS invoke(String serviceName, RQ pojoRequest, Class<RS> responseType)
	{
		if (pojoRequest == null)
		{
			LOGGER.error(ErrorMessages.NULL_REQUEST_SENDING_ERROR);
			throw new MessagingException(ErrorMessages.NULL_REQUEST_SENDING_ERROR);
		}
		String packageName = WsAnatomistUtil.getPackageName(pojoRequest.getClass());
		Service service = SERVICE_FACTORY.getService(serviceName);
		QName portQName = SERVICE_FACTORY.getPortQName(serviceName);
		Dispatch<Object> jaxbDispatch = service.createDispatch(portQName, getJaxbContext(packageName),
		        Service.Mode.PAYLOAD);
		RS response = (RS) jaxbDispatch.invoke(pojoRequest);
		return response;
	}

	private JAXBContext getJaxbContext(String contextpath)
	{
		if (JAXB_CONTEXT_MAP.containsKey(contextpath))
		{
			return JAXB_CONTEXT_MAP.get(contextpath);
		}
		else
		{
			try
			{
				JAXBContext context = JAXBContext.newInstance(contextpath);
				JAXB_CONTEXT_MAP.put(contextpath, context);
				return context;
			}
			catch (final JAXBException e)
			{
				LOGGER.error(String.format(ErrorMessages.JAXB_CONTEXT_GETTING_ERROR, contextpath));
				throw new ServiceInvokerException(e);
			}
		}
	}

}
